<template>
  <view class="swipe swipe--fix" :class="[{ 'swipe--inset': inset, 'swipe--border': getBorder }]">
    <view
      class="swipe__inner"
      :style="[
        {
          transform: `translate3d(${offset}px, 0, 0)`,
        },
      ]"
      @touchstart="onTouchStart"
      @touchmove="onTouchMove"
      @touchend="onTouchEnd"
      @touchcancel="onTouchEnd"
    >
      <view ref="leftRef" class="swipe__left">
        <slot name="left"></slot>
      </view>

      <view class="swipe__content">
        <slot name="default"></slot>
      </view>

      <view ref="rightRef" class="swipe__right">
        <slot name="right"></slot>
      </view>
    </view>
  </view>
</template>

<script lang="ts">
import {
  defineComponent,
  computed,
  reactive,
  inject,
  onMounted,
  ref,
  watch,
  onUnmounted,
  toRefs,
} from '@vue/composition-api'
import { useTouch } from '@/hooks'
import getCurrentComponent from '@/utils/get-current-component'
import boundingClientRect from '@/utils/bounding-client-rect'
import settings from '@/settings'

export type SwipePosition = 'left' | 'right' | ''
export default defineComponent({
  props: {
    name: {
      type: String,
      default: '',
    },
    disabled: {
      type: Boolean,
      default: false,
    },
    inset: {
      type: Boolean,
      default: settings.css['--card-style'] === 'card',
    },
    border: {
      type: Boolean,
      default: null,
    },
  },
  options: {
    virtualHost: true, //  将自定义节点设置成虚拟的，更加接近Vue组件的表现。我们不希望自定义组件的这个节点本身可以设置样式、响应 flex 布局等，而是希望自定义组件内部的第一层节点能够响应 flex 布局或者样式由自定义组件本身完全决定
  },
  emits: ['open', 'close'],
  setup(props, { emit }) {
    const vm: any = getCurrentComponent()
    const cellParent: any = inject('cellParent', null)
    const swipeParent: any = inject('swipeParent', null)
    const hasParent = computed(() => !!swipeParent)
    const show = ref(true)
    const leftRefWidth = ref()
    const rightRefWidth = ref()

    let opened = false
    let position: SwipePosition = ''
    let oldPosition: SwipePosition = ''

    const state = reactive({
      offset: 0,
      moving: false,
    })

    const open = (p: SwipePosition = '') => {
      opened = true
      if (p) {
        state.offset = p === 'left' ? -rightRefWidth.value : leftRefWidth.value
      }
      if (hasParent && swipeParent && swipeParent.setShow) {
        swipeParent.setShow(vm)
      }
      emit('open', {
        name: props.name,
        position: position || p,
      })
    }

    const close = () => {
      state.offset = 0
      opened = false
      emit('close', {
        name: props.name,
        position,
      })
    }

    const setoffset = (deltaX: number) => {
      position = deltaX > 0 ? 'right' : 'left'
      let offset = deltaX

      switch (position) {
        case 'left':
          if (opened && oldPosition === position) {
            offset = -rightRefWidth.value
          } else {
            offset = Math.abs(deltaX) > rightRefWidth.value ? -rightRefWidth.value : deltaX
          }
          break
        case 'right':
          if (opened && oldPosition === position) {
            offset = leftRefWidth.value
          } else {
            offset = Math.abs(deltaX) > leftRefWidth.value ? leftRefWidth.value : deltaX
          }
          break
      }

      state.offset = offset
    }

    const touch = useTouch()
    const touchMethods = {
      onTouchStart(event: Event) {
        if (props.disabled) return
        touch.start(event)
      },
      onTouchMove(event: Event) {
        if (props.disabled) return
        if (touch.isVertical() == false) {
          state.moving = true
          touch.move(event)
          setoffset(touch.deltaX.value)

          event.preventDefault()
        }
      },
      onTouchEnd() {
        if (state.moving) {
          state.moving = false
          oldPosition = position
          switch (position) {
            case 'left':
              if (Math.abs(state.offset) <= rightRefWidth.value / 2) {
                close()
              } else {
                state.offset = -rightRefWidth.value
                open()
              }
              break
            case 'right':
              if (Math.abs(state.offset) <= leftRefWidth.value / 2) {
                close()
              } else {
                state.offset = leftRefWidth.value
                open()
              }
              break
          }
        }
      },
    }
    const getBorder = computed(() => {
      if (props.border != null) {
        return props.border
      }

      if (cellParent && cellParent.border) {
        return true
      }

      return false
    })

    onMounted(async () => {
      if (swipeParent && swipeParent.addSwipe) {
        swipeParent.addSwipe(vm)
      }

      const leftBox: any = await boundingClientRect('.swipe__left', vm)
      const rightBox: any = await boundingClientRect('.swipe__right', vm)

      leftRefWidth.value = leftBox?.width
      rightRefWidth.value = rightBox?.width
    })

    onUnmounted(async () => {
      if (swipeParent && swipeParent.removeSwipe) {
        swipeParent.removeSwipe(vm)
      }
    })

    watch(
      () => show.value,
      async (newVal) => {
        // 解决初始化获取的左右不准确,每次都要计算一下
        const leftBox: any = await boundingClientRect('.swipe__left', vm)
        const rightBox: any = await boundingClientRect('.swipe__right', vm)

        leftRefWidth.value = leftBox?.width
        rightRefWidth.value = rightBox?.width
        if (!newVal) {
          close()
        }
      }
    )

    return {
      ...toRefs(state),
      ...touchMethods,
      open,
      close,
      show,
      getBorder,
    }
  },
})
</script>
<style lang="scss">
.swipe {
  position: relative;
  overflow: hidden;
  &__inner {
    position: relative;
    display: block;
    transition: all 0.3s cubic-bezier(0.19, 1, 0.22, 1);
  }
  &__left,
  &__right {
    position: absolute;
    top: 0;
    height: 100%;
  }
  &__left {
    left: 0;
    transform: translate3d(-100%, 0, 0);
  }
  &__right {
    right: 0;
    transform: translate3d(100%, 0, 0);
  }
  &__content {
    display: inherit;
  }
  &::after {
    position: absolute;
    box-sizing: border-box;
    pointer-events: none;
    right: 24rpx;
    bottom: 0;
    left: 24rpx;
    transform: scaleY(0.5);
    z-index: 0;
    border-bottom: 2rpx solid #f5f6f7;
  }
  &--inset {
    margin-left: 24rpx;
    margin-right: 24rpx;
    border-radius: 12rpx;
  }
  &--border {
    &::after {
      content: '';
    }
  }
  &:last-child {
    &::after {
      display: none;
    }
  }
}
</style>
